﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Caching;
using System.Threading;
using TNX.Common.Legacy;
using HomeFramework.Core;

namespace TNX.Common
{
    public class CacheManager : IDisposable
    {
        internal readonly MemoryCache Cache;
        internal readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

        //inizializzo il memorycache con l'hashcode dell'istanza così da poter usare diverse cache 
        public CacheManager() { Cache = new MemoryCache(GetHashCode().ToString()); }

        internal virtual string Key<T>(Func<T> driller, string[] additionalkeys)
        {
            //l'hash di un delegate non cambia mai finchè si usa lo stesso metodo anonimo 
            string k = driller.GetHashCode().ToString();
            if (additionalkeys.Length > 0)
            {
                var b = new StringBuilder(k);
                b.Append("-");
                foreach (var s in string.Join(";", additionalkeys))
                    b.Append(s);
                return b.ToString();
            }
            else
                return k;
        }

        public T GetOrDrill<T>(Func<T> driller, Func<T, bool> statusEvaluer, Expiration expiration, params string[] additionalkeys)
            where T : class
        {
            var key = Key(driller, additionalkeys);

            Lock.EnterUpgradeableReadLock();
            T risp = (T)Cache[key];

            if (risp != null && statusEvaluer != null)
            {
                if (!statusEvaluer(risp))
                {
                    Log.Debug("Poisoning cache ID " + key);
                    Lock.EnterWriteLock();
                    Cache.Remove(key);
                    Lock.ExitWriteLock();
                    risp = null;
                }
            }

            if (risp == null)
            {
                Lock.EnterWriteLock();
                risp = driller();
                Log.Debug("Adding to cache ID " + key);
                Cache.Add(key, risp, 
                    expiration.IsAbsoluteExpiration
                    ? new CacheItemPolicy() { AbsoluteExpiration = expiration.AbsoluteExpiration, RemovedCallback = OnRemovedFromCache }
                    : new CacheItemPolicy() { SlidingExpiration = expiration.SlidingExpiration, RemovedCallback = OnRemovedFromCache }
                    );
                Lock.ExitWriteLock();
            }
            Lock.ExitUpgradeableReadLock();

            return risp;
        }

        public T GetOrDrill<T>(Func<T> driller, Expiration expiration, params string[] additionalkeys)
            where T : class
        {
            return GetOrDrill<T>(driller, null, expiration, additionalkeys);
        }

        public class Expiration
        {
            public DateTime AbsoluteExpiration { get; private set; }
            public TimeSpan SlidingExpiration { get; private set; }
            public bool IsAbsoluteExpiration { get { return AbsoluteExpiration != default(DateTime); } }
            public bool IsSlidingExpiration { get { return SlidingExpiration != default(TimeSpan); } }
            
            public Expiration(DateTime absolute) { AbsoluteExpiration = absolute; }
            
            public Expiration(TimeSpan sliding) { SlidingExpiration = sliding; }
        }

        internal virtual void OnRemovedFromCache(CacheEntryRemovedArguments e)
        {
            Log.Debug("Removing from cache ID " + e.CacheItem.Key);

            if (e.CacheItem is IDisposable)
            {
                ((IDisposable)e.CacheItem).Dispose();
                Log.Debug("Disposed cache entry!");
            }
        }

        public virtual T GetOrDrillShort<T>(Func<T> driller, params string[] additionalkeys)
            where T : class
        {
            return GetOrDrill<T>(driller, new Expiration(DateTime.Now.AddMinutes(1)), additionalkeys);
        }

        public virtual T GetOrDrillLong<T>(Func<T> driller, params string[] additionalkeys)
            where T : class
        {
            return GetOrDrill<T>(driller, new Expiration(DateTime.Now.AddMinutes(10)), additionalkeys);
        }

        public virtual T GetOrDrillShortSliding<T>(Func<T> driller, params string[] additionalkeys)
            where T : class
        {
            return GetOrDrill<T>(driller, new Expiration(TimeSpan.FromMinutes(1)), additionalkeys);
        }

        public virtual T GetOrDrillLongSliding<T>(Func<T> driller, params string[] additionalkeys)
            where T : class
        {
            return GetOrDrill<T>(driller, new Expiration(TimeSpan.FromMinutes(10)), additionalkeys);
        }

        public void Dispose()
        {
            Cache.Dispose();
        }
    }
}
